/*
 * Copyright 2023-2025, Haiku, Inc. All rights reserved.
 * Distributed under the terms of the MIT License.
 */


#include "paging/LoongArchPagingMethod.h"

#include <boot/kernel_args.h>
#include <KernelExport.h>
#include <util/AutoLock.h>
#include <vm/vm.h>
#include <vm/vm_page.h>
#include <vm/VMAddressSpace.h>
#include "LoongArchVMTranslationMap.h"


//#define TRACE_LOONGARCH_PAGING_METHOD
#ifdef TRACE_LOONGARCH_PAGING_METHOD
#   define TRACE(x...) dprintf(x)
#else
#   define TRACE(x...) ;
#endif


LoongArchPagingMethod* gLoongArchPagingMethod;
extern phys_addr_t sPageTable;


LoongArchPagingMethod::~LoongArchPagingMethod()
{
}


bool
LoongArchPagingMethod::IsKernelPageAccessible(addr_t virtualAddress, uint32 protection)
{
    // 检查地址是否在内核空间范围内
    if (virtualAddress >= KERNEL_BASE && virtualAddress <= KERNEL_TOP) {
        // 内核空间地址，允许访问
        return true;
    }

    // 非内核空间地址，不允许访问
    return false;
}


status_t
LoongArchPagingMethod::MapEarly(kernel_args* args, addr_t virtualAddress,
    phys_addr_t physicalAddress, uint8 attributes,
    page_num_t (*get_free_page)(kernel_args*))
{
    TRACE("LoongArchPagingMethod::MapEarly(%#" B_PRIxADDR ", %#" B_PRIxPHYSADDR ")\n",
        virtualAddress, physicalAddress);

    // 计算页表项标志
    uint64 flags = 0;
    if ((attributes & B_KERNEL_READ_AREA) != 0)
        flags |= kPteRead;
    if ((attributes & B_KERNEL_WRITE_AREA) != 0)
        flags |= kPteWrite;
    if ((attributes & B_KERNEL_EXECUTE_AREA) != 0)
        flags |= kPteExec;

    // 添加全局页标志和访问/脏位标志
    flags |= kPteGlobal | kPteAccessed | kPteDirty | kPteValid;

    // 调用底层映射函数
    loongarch_pte* pte = LookupPteAlloc(virtualAddress, true, args, get_free_page);
    if (pte == NULL)
        return B_NO_MEMORY;

    // 设置页表项
    pte->val = (physicalAddress & kPtePhysAddrMask) | flags;

    // 刷新TLB
    FlushTLB(virtualAddress);

    return B_OK;
}


loongarch_pte*
LoongArchPagingMethod::LookupPteAlloc(addr_t virtualAddress, bool alloc,
    kernel_args* args, page_num_t (*get_free_page)(kernel_args*))
{
    loongarch_pte* pte = (loongarch_pte*)VirtFromPhys(sPageTable);
    for (int level = 3; level > 0; level--) {
        pte += VirtAddrPte(virtualAddress, level);
        if (!pte->IsValid()) {
            if (!alloc)
                return NULL;
            page_num_t ppn = get_free_page(args);
            if (ppn == 0)
                return NULL;
            memset((loongarch_pte*)VirtFromPhys(B_PAGE_SIZE * ppn), 0, B_PAGE_SIZE);

            // 创建新的页表项
            pte->val = (ppn << PAGE_SHIFT) | kPteValid | kPteRead | kPteWrite | kPteExec;
        }
        // 获取下一级页表的物理地址
        pte = (loongarch_pte*)VirtFromPhys(pte->PhysicalAddress());
    }
    pte += VirtAddrPte(virtualAddress, 0);
    return pte;
}


void
LoongArchPagingMethod::FlushTLB(addr_t virtualAddress)
{
    // 刷新指定地址的TLB
    asm volatile(
        "invtlb 0, $zero, %0\n"
        :
        : "r"(virtualAddress)
        : "memory");
}